<?php

declare(strict_types=1);

namespace app\helper;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;

use think\facade\Config;
use think\facade\Log;

use app\exception\KnownException;
use app\exception\KnownExceptionCode;
use app\helper\CommonHelper;

final class FastRequest
{
  /**
   * 获取站点种子数据
   * @param mixed $site
   * @param array $pieces_hash
   * [
   *   'pieces_hash_1' => 'pieces_hash_1',
   * ]
   * @return array
   * [
   *   'pieces_hash' => [download_url]
   * ]
   */
  public static function pieces_request($site, array $pieces_hash): array
  {
    $adapter = self::get_adapter($site['site_url']);

    $data = [
      'api_url' => $site['api_url'],
      'passkey' => $site['passkey'],
      'pieces_hash' => $pieces_hash
    ];
    $pieces_hash_torrent_id = self::guzzle_request(self::safe_get_value($adapter, 'pieces'), $data);

    if (count($pieces_hash_torrent_id) == 0) {
      return [];
    }

    // 种子库插入数据事件
    event('torrent_add', [
      'site_url' => $site['site_url'],
      'torrents' => $pieces_hash_torrent_id,
    ]);

    return self::get_download_url($site, $pieces_hash_torrent_id);
  }

  /**
   * 获取下载地址
   * @param mixed $site
   * @param array $pieces_hash_torrent_id
   * 常规如下，具体看app\config\site.php的配置情况
   * [
   *   {pieces_hash}  => {torrent_id},
   * ];
   * @return array
   */
  private static function get_download_url($site, array $pieces_hash_torrent_id): array
  {
    $adapter = self::get_adapter($site['site_url']);

    // 模板需要的数据
    $data = [
      'site_url' => $site['site_url'],
      'passkey' => $site['passkey']
    ];

    $download_url = [];
    foreach ($pieces_hash_torrent_id as $key => $value) {
      $data['torrent_id'] = $value;
      $download_url[$key] = self::template_parse($adapter['download'], $data);
    }

    return $download_url;
  }

  /**
   * 封装的一个通用guzzle请求
   * @param array $config
   * @param array $data
   * [
   *   'api_url'      =>  'api_url',
   *   'passkey'      =>  'passkey',
   *   'pieces_hash'  =>  'pieces_hash'
   * ]
   * @return array
   */
  private static function guzzle_request(array $config, array $data): array
  {
    $url = $data['api_url'];
    $request_url = self::template_parse(self::safe_get_value($config, 'request.url'), $data);
    $method = self::safe_get_value($config, 'request.method');
    $type = self::safe_get_value($config, 'request.type');

    $client = new Client();
    $parsedUrl = parse_url($url);
    $host = $parsedUrl['host'];
    $headers = array(
      'Content-Type: application/json',
      'Accept: */*',
      'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
      'Referer:' . $url,
      'Host:' . $host,
    );

    try {
      $options = [
        'headers' => $headers,
        'timeout' => 10,
        'verify' => false,
      ];

      if ($type == 'JSON') {
        $options['json'] = self::array_template_parse(self::safe_get_value($config, 'request.data'), $data);
      }

      $response = $client->request($method, $request_url, $options);
    } catch (ClientException $e) {
      $msg = '站点访问失败,链接:' . $url;
      Log::error($msg . $e->getMessage());

      // throw new KnownException($msg, KnownExceptionCode::REQUEST_NEEDCOUNT_EXCEPTION);
    } catch (ConnectException $e) {
      $msg = '站点访问超过10秒,链接:' . $url;
      Log::error($msg . $e->getMessage());

      // throw new KnownException($msg, KnownExceptionCode::REQUEST_NEEDCOUNT_EXCEPTION);
    } catch (\Exception $e) {
      $msg = '未处理的异常' . $url;
      $errormsg = $e->getMessage();
      Log::error($msg . $errormsg);

      throw new KnownException($msg . substr($errormsg, 0, 50), KnownExceptionCode::UNKNOWN_BUT_CRITICAL);
    }

    $statusCode = $response->getStatusCode();
    $content = (string) $response->getBody();
    $response->getBody()->close();

    if ($statusCode != 200) {
      $msg = '状态码:' . $statusCode . ',返回:' . substr($content, 0, 20) . $url;
      throw new KnownException($msg, KnownExceptionCode::REQUEST_NEEDCOUNT_EXCEPTION);
    }

    $response_json = json_decode($content, true);
    if (!is_array($response_json)) {
      $msg = '返回格式非json,返回:' . substr($content, 0, 50) . $url;
      // 说明不支持，该阻断就阻断，不要一直请求
      throw new \Exception($msg);
    }

    $data_key = self::safe_get_value($config, 'response.data');
    if (!isset($response_json[$data_key])) {
      $msg = '返回格式与预期不一致,返回:' . substr($content, 0, 50) . $url;
      // 说明不支持，该阻断就阻断，不要一直请求
      throw new \Exception($msg);
    }

    /**
     * 正常应该就是以下格式，有问题再兼容
     * [
     *   {pieces_hash}  => {torrent_id},
     * ];
     */
    return $response_json[$data_key];
  }

  /**
   * 获取站点的适配器
   * @param string $site_url
   * @return array
   */
  private static function get_adapter(string $site_url): array
  {
    $sites = Config::get('site.sites', []);
    $site_url = CommonHelper::site_url_standard($site_url);

    // $adapter = isset($sites[$site_url]) ?
    //          Config::get('site.adapter.' . $sites[$site_url]):
    //          Config::get('site.adapter.np');
    $adapter = Config::get('site.adapter.np');
    if (!$adapter) {
      throw new \ErrorException('配置缺失' . $site_url);
    }

    return $adapter;
  }

  /**
   * 将数组模板的值替换为实际数据
   * @param array $template
   * @param array $data
   * @return array
   */
  private static function array_template_parse(array $template, array $data): array
  {
    foreach ($template as $key => $value) {
      $template[$key] = self::template_parse($value, $data);
    }

    return $template;
  }

  /**
   * 模板替换为实际数据
   * $data暂只支持一维数组，目前够了懒的搞
   * @param string $template
   * @param array $data
   * @return mixed
   */
  private static function template_parse(string $template, array $data)
  {
    preg_match_all('/{(.*?)}/', $template, $matches);

    $keys = $matches[1];
    foreach ($keys as $key) {
      if (!isset($data[$key])) {
        throw new \InvalidArgumentException('缺少参数：' . $data[$key]);
      }
      if (is_array($data[$key])) {
        return $data[$key];
      }
      $template = str_replace('{' . $key . '}', (string) $data[$key], $template);
    }

    return $template;
  }

  /**
   * 从多纬数组中读取对应的值
   * @param array $config
   * @param string $name
   * @param mixed $default
   * @return mixed
   */
  private static function safe_get_value(array $config, string $name, $default = null)
  {
    if (empty($name)) {
      return $default;
    }

    $name = explode('.', $name);

    // 按.拆分成多维数组进行判断
    foreach ($name as $val) {
      if (isset($config[$val])) {
        $config = $config[$val];
      } else {
        return $default;
      }
    }

    return $config;
  }
}